package de.tudarmstadt.fgbs.cafe.core.configuration;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class ConfigUtility {

    /**
     * The file name of the main config file.
     */
    private static final String SYSTEM_CONFIG_FILENAME = "cafe";

    /**
     * The log which is used to log all behaviors of this class.
     */
    private static Log LOG = LogFactory.getLog(ConfigUtility.class);

    /**
     * The sub directory which extends the base directory and contains the
     * configuration files.
     */
    private static final String CONFIG_SUB_DIR = "conf/";

    /**
     * The file name extension of all configuration files.
     */
    private static final String FILE_NAME_EXTENSION = "conf";

    /**
     * A {@link Map} containing the {@link Properties} for all already known
     * config files
     */
    private static TreeMap<String, Properties> cache = new TreeMap<String, Properties>();

    /**
     * Loads the properties from the specified configuration file which has
     * {@link #FILE_NAME_EXTENSION} as file name extension.
     * 
     * @param fileName
     *            The file name without file name extension.
     * @return All entries of the configuration file or <code>null</code> if an
     *         error occurred.
     */
    public static Properties loadProperties(String fileName) {
	Properties prop;
	prop = cache.get(fileName);
	if (prop != null)
	    return prop;
	if (LOG.isTraceEnabled())
	    LOG.trace("# ConfigUtility.loadProperties(" + fileName + ")");
	// load base directory
	// TODO change to real sysprop
	// System.setProperty("cafe.base.dir",
	// "C:/Users/Chris/workspace/cafe-fpltool");
	String baseDir = System.getProperty("cafe.base.dir");
	if (baseDir == null || baseDir == "") {
	    if (LOG.isWarnEnabled())
		LOG.warn("# cafe.base.dir Path variable not set - defaulting to current directory "
			+ System.getProperty("user.dir") + " configs");
	    baseDir = System.getProperty("user.dir");
	}
	if (baseDir == null) {
	    if (LOG.isErrorEnabled())
		LOG.error("# The required base directory is not set.");
	    return null;
	}
	prop = new Properties();
	FileReader fr = null;
	try {
	    fr = new FileReader(baseDir + File.separator + CONFIG_SUB_DIR
		    + addFileNameExtension(fileName));
	    prop.load(fr);
	} catch (Exception e) {
	    if (LOG.isErrorEnabled())
		LOG.error("An error occurred while reading the ["
			+ addFileNameExtension(fileName)
			+ "] configuration file.", e);
	    return null;
	} finally {
	    if (fr != null)
		try {
		    fr.close();
		} catch (IOException e) {
		    if (LOG.isWarnEnabled())
			LOG.warn(
				"An error occurred while closing the FileReader.",
				e);
		}
	}
	if (LOG.isDebugEnabled())
	    LOG.debug("# " + prop.size() + " entries found in configuration["
		    + addFileNameExtension(fileName) + "]");
	cache.put(fileName, prop);
	return prop;
    }

    /**
     * Adds the file name extension to the given file name.
     * 
     * @param fileName
     *            The file name without file name extension.
     * @return The complete file name including its extension.
     */
    private static String addFileNameExtension(String fileName) {
	// invocation of this method is not logged, because this method is used
	// in other methods for logging informations
	return fileName + "." + FILE_NAME_EXTENSION;
    }

    /**
     * Loads the property associated with the given key from the specified
     * configuration file which has {@link #FILE_NAME_EXTENSION} as file name
     * extension.
     * 
     * @param fileName
     *            The file name without file name extension.
     * @param key
     *            The key of the requested property.
     * @return The value of the requested property or null if an error occurred.
     */
    public static String loadProperty(String fileName, String key) {
	if (LOG.isTraceEnabled())
	    LOG.trace("# ConfigUtility.getProperty(" + fileName + "," + key
		    + ")");
	Properties prop = loadProperties(fileName);
	if (prop == null)
	    return null;
	if (prop.getProperty(key) == null)
	    if (LOG.isErrorEnabled())
		LOG.error("# no entry found for key[" + key + "]");
	return prop.getProperty(key);
    }

    /**
     * Stores the given properties to the specified configuration file which has
     * {@link #FILE_NAME_EXTENSION} as file name extension. The given properties
     * are merged with the existing properties of the configuration file.
     * 
     * @param fileName
     *            The file name without file name extension.
     * @param properties
     *            The properties which should be stored.
     * @throws IOException
     *             If the resource can not be accessed.
     */
    public static void storeProperties(String fileName, Properties properties)
	    throws IOException {
	if (LOG.isTraceEnabled())
	    LOG.trace("# ConfigUtility.storeProperties(" + fileName + ", "
		    + properties + ")");
	// load base directory
	String baseDir = System.getProperty("cafe.base.dir");
	if (baseDir == null) {
	    if (LOG.isErrorEnabled())
		LOG.error("# The required CoPCoM base directory is not set.");
	    throw new IOException("No base directory found.");
	}
	// load old properties and merge with the new one
	Properties mergedProps = loadProperties(fileName);
	mergedProps.putAll(properties);
	FileWriter fw = new FileWriter(baseDir + File.separator
		+ CONFIG_SUB_DIR + addFileNameExtension(fileName));
	mergedProps.store(fw, null);
	fw.close();
	cache.remove(fileName);
	if (LOG.isInfoEnabled())
	    LOG.info("# Properties[" + addFileNameExtension(fileName)
		    + "] successfully stored.");
    }

    /**
     * Loads the properties from the system's configuration file.
     * 
     * @return All entries of the system's configuration file or
     *         <code>null</code> if an error occurred.
     */
    public static Properties loadSystemProperties() {
	if (LOG.isTraceEnabled())
	    LOG.trace("# ConfigUtility.loadSystemProperties()");
	return loadProperties(SYSTEM_CONFIG_FILENAME);
    }
}
